# coding: utf-8
import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir) 

import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict


class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 随机初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) 
        self.params['b2'] = np.zeros(output_size)

        # 生成层
        self.layers = OrderedDict()
        #ordereddict是有顺序(key被插入的先后顺序)的字典

        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        #这个Affine层其实就是在模仿层与层之间的WTX+b计算

        self.layers['Relu1'] = Relu()
        #隐藏层，带激活函数，这里的激活函数不再是sigmoid了
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])

        self.lastLayer = SoftmaxWithLoss()
        # 上述神经网络结构总共是两层：
        # 1.Relu
        # 2.Softmax
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x
        
    # x:输入数据, t:监督数据
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)


    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        #输出每行最大元素所在下标
        # 因为y是预测值，那么最大元素的下标就是被预测到的类别

        if t.ndim != 1 : #这个ndim是在检查t的维度，看下是不是二维数组
            t = np.argmax(t, axis=1)#因为这里的类别标签是独热向量，所以用argmax就可以知道下标(类别)是多少了。



        accuracy = np.sum(y == t) / float(x.shape[0])#分母是数据的总共条数
        return accuracy
        
    # x:输入数据, t:监督数据
# 下面两个函数的区别是（根据课本P139）：
# numerical_gradient是基于数值微分计算参数的梯度
# gradient是基于误差反向传播法计算梯度
# 速度上后者占有，所以顶层文件中只使用gradient函数，不使用numerical_gradient函数，也即是说，两者二选一即可。

    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        #∂E/∂W1,也就是loss_W对于self.params['W1']求导
        #直接求导的好处是不用在代码中写一大串的链式法则，这样具备更广泛的通用性。
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        return grads
        
    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())#这个list的长度是，Affine、Relu、Affine
        layers.reverse()#所有层都反向排列一遍，便于反向传播“累乘”
        for layer in layers:
            dout = layer.backward(dout)

        # 设定
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db



        return grads
